#include "ring_mem.h"

RingMem::RingMem(size_t size): total_size_(size) {
  addr_ = new char[total_size_];
  if (addr_) {
    memset(addr_, 0, total_size_);
  } else {
    std::cerr << "RingMem new failed" << std::endl;
  }
}
RingMem::~RingMem() {
  if (addr_) {
    delete []addr_;
  }
}

bool RingMem::IsEmpty() const {
  return use_ == 0 ? true : false;
}

bool RingMem::IsFull() const {
  return use_ == total_size_ ? true : false;
}

size_t RingMem::get_value() const {
  return total_size_ - use_;
}

size_t RingMem::get_use() const {
  return use_;
}

void RingMem::PushData(const char *data, size_t num) {
  if (IsFull()) {
    printf("addr_ IsFull, push err!!!\n");
    return;
  }

  if (num > get_value()) {
    printf("push data too large, add is %ld, value is %ld\n", num, get_value());
    return;
  }

  // + is value pos
  //         0 1 2 3 4 5 6 7 8 9
  // case 1 rw + + + + + + + + +    r = w
  // case 2  r         w + + + +    r < w
  // case 3  + + + r   w + + + +    r < w
  // case 4  w + + + + r            w < r
  // case 5      w + + + r          w < r
  if (pos_w_ >= pos_r_) {
    if (total_size_ - pos_w_ >= num) {      // case 1 2
      memcpy(&addr_[pos_w_], &data[0], num);
      pos_w_ += num;
      pos_w_ %= total_size_;
    } else {                                // case 3
      size_t cpy_size_1 = total_size_ - pos_w_;
      size_t cpy_size_2 = num - cpy_size_1;
      memcpy(&addr_[pos_w_], &data[0], cpy_size_1);
      memcpy(&addr_[0], &data[cpy_size_1], cpy_size_2);
      pos_w_ = cpy_size_2;
    }
    use_ += num;
  } else if (pos_w_ < pos_r_) {             // case 4 5
    memcpy(&addr_[pos_w_], &data[0], num);
    pos_w_ += num;
    pos_w_ %= total_size_;
    use_ += num;
  } else {
    printf("push err\n");
  }
}

//         0 1 2 3 4 5 6 7 8 9
// case 1 rw                      r = w
// case 2  r + + + + w            r < w
// case 3        r + + + w        r < w
// case 4  w         r + + + +    w < r
// case 5  + + w       r + + +    w < r
void RingMem::PopData(char *data, size_t num) {
  if (num > use_) {
    printf("num>use == %ld>%ld, PopData err!!!\n", num, use_);
    return;
  }

  if (pos_w_ >= pos_r_) {                 // case 1 2 3
    memcpy(&data[0], &addr_[pos_r_], num);
    pos_r_ += num;
    pos_r_ %= total_size_;
    use_ -= num;
  } else if (pos_w_ < pos_r_) {
    if (total_size_ - pos_r_ >= num) {    // case 4
      memcpy(&data[0], &addr_[pos_r_], num);
      pos_r_ += num;
      pos_r_ %= total_size_;
    } else {                              // case 5
      size_t cpy_size_1 = total_size_ - pos_r_;
      size_t cpy_size_2 = num - cpy_size_1;
      memcpy(&data[0], &addr_[pos_r_], cpy_size_1);
      memcpy(&data[cpy_size_1], &addr_[0], cpy_size_2);
      pos_r_ = cpy_size_2;
    }
    use_ -= num;
  } else {
    printf("pop err\n");
  }
}

void RingMem::ReadData(char *data, size_t num) {
  if (num > use_) {
    printf("num>use == %ld>%ld, ReadData err!!!\n", num, use_);
    return;
  }

  if (pos_w_ >= pos_r_) {                 // case 1 2 3
    printf("[debug] read 0\n");
    memcpy(&data[0], &addr_[pos_r_], num);
  } else if (pos_w_ < pos_r_) {
    if (total_size_ - pos_r_ >= num) {    // case 4
      printf("[debug] read 1\n");
      memcpy(&data[0], &addr_[pos_r_], num);
    } else {                              // case 5
      printf("[debug] read 2\n");
      size_t cpy_size_1 = total_size_ - pos_r_;
      size_t cpy_size_2 = num - cpy_size_1;
      memcpy(&data[0], &addr_[pos_r_], cpy_size_1);
      memcpy(&data[cpy_size_1], &addr_[0], cpy_size_2);
    }
  } else {
    printf("read err\n");
  }
}

// for debug
void RingMem::printf_mem() {
  printf("+++++++++++++++\n");
  for (size_t i = 0; i < total_size_; i++) {
    printf("%2d ", addr_[i]);
  }
  printf("use/total: %ld/%ld", use_, total_size_);
  printf("\n");
  for (size_t i = 0; i < total_size_; i++) {
    if (pos_r_ == pos_w_) {
      printf(" rw");
      break;
    }
    if (i == pos_r_) {
      printf(" r ");
    } else if (i == pos_w_) {
      printf(" w ");
    } else {
      printf("   ");
    }
  }
  printf("\n+++++++++++++++\n");
}

// for debug
bool RingMem::is_same(size_t num, char *data, size_t r, size_t w, size_t use) {
  if (num != total_size_) {
    printf("num err\n");
    return false;
  }
  for (size_t i = 0; i < total_size_; i++) {
    if (addr_[i] != data[i]) {
      printf("addr_%ld err\n", i);
      return false;
    }
  }

  if (r != pos_r_) {
    printf("r err\n");
    return false;
  }
  if (w != pos_w_) {
    printf("w err\n");
    return false;
  }
  if (use != use_) {
    printf("use err\n");
    return false;
  }

  return true;
}

// for debug
void RingMem::set_arr(char value) {
  memset(addr_, value, total_size_);
}
void RingMem::set_w(size_t idx) {
  pos_w_ = idx;
}
size_t RingMem::get_w() {
  return pos_w_;
}
void RingMem::set_r(size_t idx) {
  pos_r_ = idx;
}
size_t RingMem::get_r() {
  return pos_r_;
}
void RingMem::set_use(size_t idx) {
  use_ = idx;
}
size_t RingMem::get_use() {
  return use_;
}
void RingMem::set_r_w_user(size_t r, size_t w, size_t use) {
  pos_r_ = r;
  pos_w_ = w;
  use_ = use;
}

